
<!--
 * @Description: 
 * @Version: 2.0
 * @Autor: wangmiao
 * @Date: 2020-07-26 13:14:35
 * @LastEditors: wangmiao
 * @LastEditTime: 2020-07-26 13:45:51
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<!-- VUE是不可能兼容IE的 -->

<body>
    <div id="app">
        <h1 class='box'>{{name}}哈哈哈哈{{age}}</h1>
        <input type="text" v-model='name'>

        <h2>{{age}}</h2>
        <input type="text" v-model='age'>
    </div>

</body>

</html>
<script src=""></script>
<script>
    
    
    function observe(data){
        if(({}).toString.call(data).match(/(\w+)]/)[1]!='Object') return;//递归终止条件
        let keys = Object.keys(data);
        keys.forEach((key)=>{
            defineReactive(data,key,data[key])
        })
    }
    function defineReactive(obj,key,value){
        observe(value);//递归劫持
        let dep = new Dep();
        //负责使用 definProperty进行数据劫持
        Object.defineProperty(obj,key,{
            get(){
                if(Dep.target){
                    dep.addSub(Dep.target);
                }
                return value;
            },
            set(newValue){
                console.log('set')
                if(value!== newValue){
                    value = newValue;
                    dep.notify()       
                }
                
            }
        })
    }
    //进行
    function nodeToFragment(el,vm){
        //把节点放到fragment
        let fargment = document.createDocumentFragment();
        let child;
        while(child = el.firstChild){
            fargment.appendChild(child);
            //放入空白容器中进行编译器的加工，最后在放回到el里面
            compile(child,vm);
        }
        el.appendChild(fargment);
    } 
    //目前只考虑编译v-model和v-text
    function compile(node,vm){
        //编译node中的vue语法
        //先判断nodeTyoe节点类型，看他是元素节点还是文本节点
        //1,3,8,9
        if(node.nodeType == 1){
            //元素节点，需要获取行内属性
           let attrs = node.attributes;
            [...attrs].forEach(item=>{
                //查看当前的属性名是不是v-xxxx
                if(/^v-/.test(item.name)){
                    //证明这个属性是v-model要获取里面的值
                    let vName = item.nodeValue;
                    let val = vm.$data[vName]; 
                    new Watcher(node,vName,vm)
                    node.value = val;
                    node.addEventListener('input',(e)=>{
                        vm.$data[vName] = e.target.value; 
                    })
                }
            });
            [...node.childNodes].forEach(item=>{
                compile(item,vm)
            })
        }else{
            let str = node.textContent;
            if(/\{\{(.+?)\}\}/.test(str)){
                str = str.replace(/\{\{(.+?)\}\}/,(a,b)=>{
                    new Watcher(node,b,vm)
                    return vm.$data[b];
                })
                node.textContent = str;
            }
        }
    }
//订阅器
class Dep{
    constructor(){
        this.subs = [];
    }
    addSub(sub){
        this.subs.push(sub)
    }
    notify(){
        this.subs.forEach(sub=>{
            sub.update();
        })
    }
}

class Watcher{
    constructor(node,key,vm){
        Dep.target = this;
        this.node = node;
        this.key = key;
        this.vm = vm;
        this.getValue();
        Dep.target =null;
    }
    update(){
       this.getValue();
        if(this.node.nodeType==1){
            //input框
            this.node.value = this.value;
        }else{
            this.node.textContent = this.value;
        }
    }
    getValue(){
        this.value = this.vm.$data[this.key]
    }
}
    function Vue(options){
        this.$el = document.querySelector(options.el);
        this.$data = options.data;
        observe(this.$data)//负责数据劫持
        nodeToFragment(this.$el,this);//负责模板编译，把vue的语法进行转换
    }
    let vm = new Vue({
        //vue的配置
        //这里的属性都是vue规定
        el: '#app',
        data: {
            //vue中的变量都是属性
            //这里的属性都会挂载到当前vue实例上
            name: "胡的搜索",
            age:18,
        },
        methods: {
            changeValue(e) {
                this.name = e.target.value
            }
        }
    })
</script>
